﻿using System.Linq.Expressions;
using ShouldIt.Core;

namespace ShouldIt.Clr.Fluent
{
    public class BeMethodCallExpression
    {
        private readonly Should<MethodCallExpression, BeMethodCallExpression> should;
        private readonly IAssertProvider assertProvider;

        public BeMethodCallExpression(Should<MethodCallExpression, BeMethodCallExpression> should, IAssertProvider assertProvider)
        {
            this.should = should;
            this.assertProvider = assertProvider;
        }

        public MethodCallExpression Equal(MethodCallExpression expectedCall)
        {
            var bothNull = should.Target == null && expectedCall == null;
            var methodsAreUnEqual = !bothNull && 
                (should.Target == null || expectedCall == null || !should.Target.Method.Equals(expectedCall.Method) || !AllArgsAreEqual(expectedCall));

            if (should.Negate && !methodsAreUnEqual)
            {
                assertProvider.Fail("Expected method calls to be unequal, but they were.");
            }
            else if (!should.Negate && methodsAreUnEqual)
            {
                assertProvider.Fail("Expected method calls to be equal, but they were not.");
            }
            return should.Target;
        }

        private bool AllArgsAreEqual(MethodCallExpression expectedCall)
        {
            for (var i = 0; i < should.Target.Arguments.Count; i++)
            {
                var targetArgValue = Expression.Lambda(should.Target.Arguments[i]).Compile().DynamicInvoke();
                var expectedArgValue = Expression.Lambda(expectedCall.Arguments[i]).Compile().DynamicInvoke();
                var bothNull = targetArgValue == null && expectedArgValue == null;
                if (!bothNull && (targetArgValue == null ||  expectedArgValue == null || !targetArgValue.Equals(expectedArgValue)))
                {
                    return false;
                }
            }
            return true;
        }
    }
}